package controler;

import controler.ProtocolException;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class Protocol {
	
	private static final String SEPARATOR = ":";	
	
	/**
	 * Methode de parsage du protocol
	 * @param entree le flux d'entree
	 * @param from 1: from joueur    0: from serveur 
	 * @throws Exception
	 */
	public static void parser(BufferedReader entree) throws IOException, ProtocolException {
		HashMap<String, String> h = new HashMap<String, String>(); 
		String s;
		while((s=entree.readLine())!= null ){ 
			System.out.println(s);
			if (s.equals("FIN")){
				break;
			}
			try{
				Pattern pattern = Pattern.compile("^(\\w*)"+SEPARATOR+"(.*)$");
				Matcher matcher = pattern.matcher(s);
				if (matcher.find()) {
					String action = matcher.group(1);
					String values = matcher.group(2);
					h.put(action, values);
				} else {
					throw new ProtocolException("Ne peut parser la ligne", h);
				}
			}catch(Exception e){
				System.err.println("Erreur de parsing, ne peut splitter sur ':'");
				e.printStackTrace();
				return;
			}
		}
		protocolHandler(h);
	}
	
	/**
	 * Methode qui appelle les differentes fonctions de communication
	 * @param h la hashmap parse par le protocol
	 * @throws ProtocolException
	 */
	private static void protocolHandler(HashMap<String, String> h) throws ProtocolException{
		String type = h.get("type");
		
		if (type.equalsIgnoreCase("chat")){
			rcv_chat(h.get("prefix"),h.get("data"), h.get("from"),h.get("table"),h.get("couleur"));
			return;
		}
		if(type.equalsIgnoreCase("joueur")){
			rcv_joueur(h.get("pseudo"),h.get("status"), h.get("table"),h.get("couleur"));
			return;
		}
		if(type.equalsIgnoreCase("connect")){
			rcv_connect(h.get("status"));
			return;
		}
		if(type.equalsIgnoreCase("auth")){
			rcv_otp(h.get("seed"), h.get("sequence"));
			return;
		}
	}
	/**
	 * On est client, on recoit la reponse du serveur par rapport au challenge auquel
	 * on a repondu.
	 * @param string
	 */
	private static void rcv_connect(String string) {
		if(string.equalsIgnoreCase("ok")){
			Joueur.ui.remove(Joueur.ui.loginPanel);
			Joueur.ui.add(Joueur.ui.chatPanel);
			Joueur.ui.validate();
			
	    	Joueur.ui.statutPanel.setIconConnected(true);
	    	Joueur.ui.statutPanel.setStatus("Bienvenue "+Joueur.name+" !");
	    	Joueur.status = Joueur.CONNECTED;
		} else {
			Joueur.ui.statutPanel.setStatus("Mot de passe incorrect !");
		}
	}
	
	/**
	 * On est client et on reçoit une communication de type chat
	 * @param s
	 */
	private static void rcv_chat(String prefix,String s, String from,String table,String couleur){
		boolean isFromServeur;
		if(from.equalsIgnoreCase("Serveur")){
			isFromServeur = true;
		} else {
			isFromServeur = false;
			prefix += from;
		}
		Joueur.ui.chatPanel.displayMsg(prefix,s,Joueur.stringToColor(couleur),isFromServeur);
	}
	
	/**
	 * On est client et on envoie une communication de type chat
	 * @param s les donnees a envoyer
	 * @param pseudo le pseudo de qui viennent ces donnees
	 * @param table le nom de la table sur laquelle afficher les donnees
	 * @throws ProtocolException 
	 */
	public static void snd_chat(String s, String pseudo,String table,String couleur) throws ProtocolException{
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "chat");
		hp.put("data", s);
		hp.put("table", table);
		hp.put("couleur", couleur);
		formatToProtocol(Joueur.sortie,hp);
	}
	
	/**
	 * On est client et on envoie une demande de connexion
	 * @param name nom
	 * @param pass mot de passe
	 * @throws ProtocolException 
	 */
	public static void snd_connect(String name, String pass) throws ProtocolException {
		HashMap<String,String> hp = new HashMap<String,String>();
		hp.put("type", "connect");
		hp.put("pseudo",name);
		hp.put("pass",pass);
		formatToProtocol(Joueur.sortie, hp);
	}
	
	/**
	 * On est client et on recoit un joueur a mettre a jour dans la liste des joueurs 
	 * @param pseudo
	 * @param status
	 */
	private static void rcv_joueur(String pseudo, String status, String table, String couleur){
		if(pseudo.equals(Joueur.name)){ // C'est moi !
			Joueur.couleur = Joueur.stringToColor(couleur); // je set ma couleur donnee par le serveur
			Joueur.ui.chatPanel.msgText.setForeground(Joueur.couleur);
		}
		if (status.equalsIgnoreCase("connected")){
			// on rejoint la salle !
			Joueur.ui.chatPanel.addToPseudoList(pseudo,Joueur.stringToColor(couleur)); // envoie sur le chat principal
		}
	}

	/**
	 * Reception de la graine et du numero de sequence venant du serveur
	 * pour l'identification One Time Password (otp)
	 * @param seed
	 * @param sequence
	 */
	private static void rcv_otp(String seed, String sequence){
		char[] tmp = Joueur.ui.loginPanel.passTextField.getPassword();
		String password = "";
		for (int i = 0; i < tmp.length; i++) {
			password += tmp[i];
		}
		
		byte[] otp = null;
		try {
			otp = Auth.compute(seed, password, Integer.parseInt(sequence));
			String hash = new BigInteger(otp).toString(16);
			snd_otp(hash);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (ProtocolException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Envoie du challenge resolu par le client.
	 * @param hash
	 * @throws ProtocolException 
	 */
	public static void snd_otp(String hash) throws ProtocolException{
		HashMap<String, String> hp = new HashMap<String, String>();
		hp.put("type", "auth");
		hp.put("otp", hash);
		formatToProtocol(Joueur.sortie,hp);
	}
	
	/**
	 * Envoie les donnees dans le format du protocol en verifiant 
	 * qu'elles sont bien formees.
	 * @param p 
	 * @param hp
	 */
	public static void formatToProtocol(PrintWriter p,HashMap<String, String> hp ) throws ProtocolException {
		try{
			hp.get("type");
		}catch(Exception e){
			throw new ProtocolException("Il n'y a pas de 'type' pour cet envoie de donnees");
		}
		for(Entry<String, String> entry : hp.entrySet()) {
		    String cle = entry.getKey();
		    String valeur = entry.getValue();
		    
		    // verif sur la cle et la valeur
		    if(cle.matches(":")){ // la regexp du parser prend le 1er ':' donc il en faut pas ici
		    	throw new ProtocolException("La cle contient ':'");
		    }
		    // Ecriture des donnees
		    p.println(cle+":"+valeur);
		}
		p.println("FIN");
		p.flush();
	}
}



